#include <OneWire.h>
#include <Wire.h>  
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>
#include "PCD8544.h"
#include <Time.h>  
//#include <DS1307RTC.h> 
#include <dht11.h>
#include <IRremote.h>

//time
unsigned long timeWasSet = 0;
unsigned long lastTimeCheck = 0;
const unsigned long timeCheckInterval = 60000;

//temperature
unsigned long lastTemperatureCheck = 0;
const unsigned long temperatureCheckInterval = 30*1000;
int oneWirePIN = 2;
const int addressLength = 8;
byte onBoardSensorAddress[] = {0x28,0xA8,0xD9,0x9E,0x03,0,0,0x5E};
byte onSoilAddress[] = {0x28,0xf5,0xC9,0x9E,0x03,0,0,0x46};
byte onWaterAddress[] = {0x28,0x06,0xC5,0xA9,0x03,0,0,0x82};

float boardTemp = 0.0;
float soilTemp = 0.0;
float waterTemp = 0.0;

OneWire  dsOneWire(oneWirePIN);


// relay timers
unsigned long lastRelayCheck = 0;
const unsigned long relayCheckInterval = 10*1000;
boolean lastLightState = false;
boolean lastHumidifierState = false;

//moisture
int moisturePIN =  A0;
int mositureVoltagePin = 3;
float moisture = 0.0;
unsigned long lastMoistureCheck = 0;
const unsigned long moistureCheckInterval = 30*1000;
float wateringThreshold = 60.0;
int wateringTime = 2000;

float moistureSum = 0.0;
int moistureCount = 0;
unsigned long lastMoistureAggregate = 0;
const unsigned long moistureAggregateInterval = 3600000;// 1hour

float lastMoistures[10];
int  lastMoisturesCount = 10;
//display
PCD8544 nokia = PCD8544(16, 15, 10, 6);
unsigned long lastDisplayUpdate = 0;
const unsigned long displayUpdateInterval = 1*1000;
int displayIndex = 0;
int displayCount =  2; 
boolean bUpdateFlag = false;

//light
int lightPIN = 4;
boolean bForceLightOn = false;

//PUMP
int pumpPIN = 5;
void waterPlant(){
    Serial.println("Watering plant");
    digitalWrite(pumpPIN,LOW);
    delay(wateringTime);
    digitalWrite(pumpPIN,HIGH);  
}
//humidifier
int humidifierPort = 7;


// humidity
dht11 DHT11;
int pinDHT11 = 8;
float humidity = 0.0;
  unsigned long       lastHunidityCheck = 0;
  const unsigned long humidityCheckInterval = 30*1000;

// IR receiver
int irPIN = 9;
IRrecv irrecv(irPIN);
decode_results ir_results;

boolean bOn = true;
const char* nodeName = "plant";
const char* serverName = "serv1";
boolean bDateSet = false;
unsigned long lastTimeRequest = 0;


unsigned long lastStatusChange = 0;
boolean  bStatusBlink = false;

time_t currTime;

void serialSensor(const char* name,float value){
  Serial.print("sensor:");
  Serial.print(name);
  Serial.print(":");
  Serial.println(value);
}
#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
#define TIME_HEADER  'T'   // Header tag for serial time sync message
#define TIME_REQUEST  7    // ASCII bell character requests a time sync message 

void setup() {
  Serial.begin(9600);
  
  pinMode(13, OUTPUT);     
  while (1){
      if(Serial.available() >=  TIME_MSG_LEN) 
      {
          char c = Serial.read() ; 
          if( c == TIME_HEADER ) {       
            time_t pctime = 0;
            for(int i=0; i < TIME_MSG_LEN -1; i++){   
              c = Serial.read();          
              if( c >= '0' && c <= '9'){   
                pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
              }
            }   
            setTime(pctime);   // Sync Arduino clock to the time received on the serial port
          }  
          timeWasSet = 1;
          digitalWrite(13, HIGH);
          break;
      }    
     digitalWrite(13, HIGH);
     delay(500);
     digitalWrite(13, LOW);
     delay(500);
     
  }
  //currTime = DS1307RTC::get();
  //setTime(currTime);
  
  //setSyncProvider(RTC.get);   // the function to get the time from the RTC
  //setSyncInterval(60);
  //if(timeStatus()!= timeSet) 
  //   Serial.println("Unable to sync with the RTC");
  //else
  //   Serial.println("RTC has set the system time");      
  
  nokia.init();
  // you can change the contrast around to adapt the display
  // for the best viewing!
  nokia.setContrast(50);
  nokia.command(PCD8544_DISPLAYCONTROL | PCD8544_DISPLAYNORMAL);   
 
  //light
   pinMode(lightPIN,OUTPUT);
   digitalWrite(lightPIN,HIGH);
  //pump
   pinMode(pumpPIN,OUTPUT);
   digitalWrite(pumpPIN,HIGH);
   //humidifier 
   pinMode(humidifierPort,OUTPUT);
   digitalWrite(humidifierPort,HIGH);
   
   // init last moistures for debugging
   for (int j=0;j<lastMoisturesCount;j++)
      lastMoistures[j] = 0.0;
   lastMoistureAggregate = millis();      
   //moisture
   pinMode(mositureVoltagePin,OUTPUT);
   digitalWrite(mositureVoltagePin,LOW);
      
   irrecv.enableIRIn(); // Start the receiver   
}

void loop() {
  while(Serial.available())
    Serial.read();
  if ( ( millis() - lastTimeCheck  > timeCheckInterval ) || lastTimeCheck==0){
       //Serial.println(millis() - lastMoistureAggregate);
        //Serial.println(moistureAggregateInterval);
        lastTimeCheck = millis();
        
        Serial.print("Time to next watering check: ");
        Serial.print((moistureAggregateInterval-(millis() - lastMoistureAggregate))/1000);
        Serial.print(" sec; estimated moisture: ");
        if (moistureCount==0)
           Serial.print("unknown");
        else
           Serial.print(moistureSum/moistureCount);
           
        Serial.print("; watering threshold: ");
        Serial.println(wateringThreshold);   
       
        
        Serial.print("arduinoTime:");

        int h = hour();
        if (h<10)
           Serial.print("0");
        Serial.print(h);
        Serial.print(":");
   
        int m = minute();
        if (m<10)
           Serial.print("0");
        Serial.print(m);
        Serial.print(":");      
   
        int s = second();
        if (s<10)
           Serial.print("0");
        Serial.print(s);
        Serial.println();
        /*
        time_t rtc = DS1307RTC::get();
        Serial.print("rtcTime:");
        h = hour(rtc);
        if (h<10)
           Serial.print("0");
        Serial.print(h);
        Serial.print(":");
   
        m = minute(rtc);
        if (m<10)
           Serial.print("0");
        Serial.print(m);
        Serial.print(":");      
   
        s = second(rtc);
        if (s<10)
           Serial.print("0");
        Serial.print(s);
        Serial.println();*/
      
  }
  
  // measure temperature
  if ( ( millis() - lastTemperatureCheck  > temperatureCheckInterval ) || lastTemperatureCheck==0){
      boardTemp = getTemperature(onBoardSensorAddress);
      serialSensor("air_temp",boardTemp);

      soilTemp = getTemperature(onSoilAddress);
      serialSensor("soil_temp",soilTemp);

      waterTemp = getTemperature(onWaterAddress);
      serialSensor("water_temp",waterTemp);

      lastTemperatureCheck = millis();
  }
  // mesaure soil moisture
  if ( ( millis() - lastMoistureCheck  > moistureCheckInterval ) || lastMoistureCheck==0){
    digitalWrite(mositureVoltagePin,HIGH);
    delay(500);
    analogRead(moisturePIN);
    int iSum = 0;
    const int probeCount = 20;
    for (int i=0;i<probeCount;i++){
         int sensorValue = analogRead(moisturePIN);
         iSum = iSum + sensorValue;
    }
    moisture = ((iSum/probeCount)/1024.0)*100.0;
    digitalWrite(mositureVoltagePin,LOW);

    moistureSum=moistureSum+moisture;
    moistureCount++;
   // Serial.print(";moistureCount:");
   // Serial.println(moistureCount);  

    serialSensor("soil_moisture",moisture);
    
    lastMoistureCheck = millis();
  }  
  if ((millis() - lastMoistureAggregate)  > moistureAggregateInterval){
      //Serial.println("at last!");
      if (moistureCount==0)
        Serial.println("ERROR!moistureCount=0");
      else{
        moisture = moistureSum/moistureCount;
        moistureSum = 0;
        moistureCount = 0;
        serialSensor("soil_moisture_aggr",moisture);
        if (moisture<10 || moisture>90){
            Serial.print("Invalid aggregated moisture value:");
            Serial.println(moisture);
        }else{
            if (moisture<wateringThreshold){
                Serial.println("Plan moisture automatically");
                waterPlant();
            }  
        }
     }
      for (int i=lastMoisturesCount-1;i>0;i--){
          lastMoistures[i] = lastMoistures[i-1];
      }      
      lastMoistures[0] = moisture;
      Serial.print("lastMoistures : ");
      for ( int i=0;i<lastMoisturesCount;i++){
          Serial.print( lastMoistures[i],0);
          Serial.print(" ");
      }      
      Serial.println();
      
      lastMoistureAggregate = millis();
  }
  
  //measure humidity
  if ( ( millis() - lastHunidityCheck  > humidityCheckInterval ) || lastHunidityCheck==0){
        //measure air humidity
          int chk = DHT11.read(pinDHT11);
      
         // Serial.print("Read sensor: ");
          switch (chk)
          {
             case 0:  break;
             case -1: Serial.println("Checksum error"); break;
             case -2: Serial.println("Time out error"); break;
             default: Serial.println("Unknown error"); break;
        }
        if (chk==0){
          humidity = DHT11.humidity;
          serialSensor("humidity",humidity); 
        }
        lastHunidityCheck = millis();
  }

 //update UI  
 if ( ( millis() - lastDisplayUpdate  > displayUpdateInterval ) || lastDisplayUpdate==0 || bUpdateFlag){
      bUpdateFlag = false;

      nokia.clear();
      
      if (displayIndex==0){
        nokia.setCursor(0, 0);
        int h = hour();
        if (h<10)
           nokia.print("0");
        nokia.print(h);
        nokia.print(":");
   
        int m = minute();
        if (m<10)
           nokia.print("0");
        nokia.print(m);
        nokia.print(":");      
   
        int s = second();
        if (s<10)
           nokia.print("0");
        nokia.print(s);
        nokia.println();
  
        nokia.print(moisture,1);
        nokia.print("M ");
        nokia.print((int)humidity);
        nokia.println("H");
        
        nokia.print("A");
        nokia.print(boardTemp,1);
        nokia.print("C ");
        
        nokia.print("S");
        nokia.print(soilTemp,1);
        nokia.println("C");

        nokia.print("W");
        nokia.print(waterTemp,1);
        nokia.println("C");
      
      }else if (displayIndex==1){
         nokia.setCursor(0, 0);

        //"% "+String((int)temperature)+"C");  
     // init last moistures for debugging
        for (int i=0;i<lastMoisturesCount;i++){
            nokia.print( lastMoistures[i],0);
            nokia.print(" ");
        }
      }
      nokia.display();      
      
      lastDisplayUpdate = millis();
  }  
  // ir results
 if (irrecv.decode(&ir_results)) {
    //Serial.println(ir_results.value, HEX);
     irrecv.resume(); // Receive the next value
    
    if (ir_results.decode_type == NEC && ir_results.bits==32){
      //Serial.print("IR:");
      //Serial.println(ir_results.value, HEX); 
      unsigned long request = ir_results.value;
      //Serial.println(request, HEX); 
      unsigned int device = (request>>16);
      //Serial.println(device, HEX); 
      if (device==0xFF){//our remote control
          byte  command = (request & 0xFF00)>>8;
          byte  command_inverted = request & 0x00FF;
          
          // Serial.println(command, HEX); 
           //Serial.println(command_inverted, HEX); 
           //Serial.println(~command_inverted, HEX); 
           
           if (command==(byte)~command_inverted){
              //Serial.print("IR command:");
              //Serial.println(command, HEX); 
              if (command==0xA2){
                  bForceLightOn = !bForceLightOn; 
                  //if (bForceLightOn)
                  //  Serial.println("Light Force is on");
                  //else
                  //  Serial.println("Light Force is off");
              }else if (command==0xE2){
                 //Serial.println("pump on!");
                 waterPlant();
              }else if (command==0x62){
                 displayIndex++;
                 if (displayIndex>=displayCount)
                    displayIndex = 0;
                 bUpdateFlag = true;
                 //Serial.print("display mode:");
                 //Serial.println(displayIndex);
              }
             
           }
           
      }
    } 
 }  
 if ( lastRelayCheck==0 || ( millis() - lastRelayCheck  > relayCheckInterval )){
     //light
     boolean bLight =  (hour()>=6 && hour()<21) || bForceLightOn;
     if (lastLightState!=bLight){
        lastLightState = bLight;
        digitalWrite(lightPIN,bLight?LOW:HIGH);
     }
     //humidifier
     boolean bHumidifer = (minute()>=0 && minute() < 30);
     if (lastHumidifierState!=bHumidifer){
       lastHumidifierState = bHumidifer;
       digitalWrite(humidifierPort,bHumidifer?LOW:HIGH);
     }
 }
}
//////////////////////////////////////////
void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}
/////////////////////////////
float getTemperature(byte address[]){
  byte i;
  byte present = 0;
  byte data[12];
  
  // read the value from the sensor:
 // int sensorValue = analogRead(sensorPin);   
  //Serial.println(sensorValue); 
   // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  // print the number of seconds since reset:
 // lcd.print(String("M")+String(sensorValue));
 /*
  if ( !dsTemperature.search(addr)) {
      Serial.print("No more addresses.\n");
      dsTemperature.reset_search();
      return -1;
  }
  dsTemperature.reset_search();
  

  Serial.print("R=");
  for( i = 0; i < 8; i++) {
    Serial.print(addr[i], HEX);
    Serial.print(" ");
  }
  */

  if ( OneWire::crc8( address, 7) != address[7]) {
      Serial.print("CRC is not valid!\n");
      return -1;
  }

  if ( address[0] == 0x10) {
      //Serial.print("Device is a DS18S20 family device.\n");
  }
  else if ( address[0] == 0x28) {
     // Serial.print("Device is a DS18B20 family device.\n");
  }
  else {
      Serial.print("Device family is not recognized: 0x");
      Serial.println(address[0],HEX);
      return -1;
  }

  dsOneWire.reset();
  dsOneWire.select(address);
  dsOneWire.write(0x44,1);         // start conversion, with parasite power on at the end

  delay(750);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.

  present = dsOneWire.reset();
  dsOneWire.select(address);    
  dsOneWire.write(0xBE);         // Read Scratchpad

 // Serial.print("P=");
 // Serial.print(present,HEX);
 // Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = dsOneWire.read();
 ///   Serial.print(data[i], HEX);
 //   Serial.print(" ");
  }
//  Serial.print(" CRC=");
//  Serial.print( OneWire::crc8( data, 8), HEX);
//  Serial.println();
  int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
  LowByte = data[0];
  HighByte = data[1];
  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // test most sig bit
  if (SignBit) // negative
  {
    TReading = (TReading ^ 0xffff) + 1; // 2's comp
  }
  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

  Whole = Tc_100 / 100;  // separate off the whole and fractional portions
  Fract = Tc_100 % 100;

  float temperature = Whole;
  temperature+=Fract/100.0;
  if (SignBit) // If its negative
     temperature = -temperature;
  return temperature;
}

